﻿using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;
using System.Transactions;
using Autofac;
using Microsoft.Practices.ServiceLocation;
using NQueries.Data;
using NQueries.Data.Base;
using NQueries.Data.EF.Compiler;
using NQueries.Data.Queries;
using NQueries.Utils;
using TestModel;

namespace TestConsole
{
    public static class AutofacContainerBuilder
    {
        public static IContainer Build()
        {
            ContainerBuilder containerBuilder = new ContainerBuilder();

            containerBuilder.RegisterType<AdventureWorksEntities>().As<IDataContext>().ExternallyOwned().InstancePerDependency();
            containerBuilder.RegisterType<QueryCompiler>().As<IQueryCompiler>().ExternallyOwned().InstancePerDependency();
            containerBuilder.RegisterType<DataFactory>().As<IDataFactory>().ExternallyOwned().SingleInstance();

            return containerBuilder.Build();
        }
    }

    internal class Program
    {
        static void Main()
        {
            // Setup dependency injection through service locator
            IContainer container = AutofacContainerBuilder.Build();
            ServiceLocator.SetLocatorProvider(() => new AutofacServiceLocator(container));

            // precompile all queries in given assembly
            DataFactory.Current.CompileQueries(typeof(AdventureWorksEntities), Assembly.GetAssembly(typeof(QueryAllProducts)));

            while (true)
            {
                CheckNestedBeginOrJoin();
                CheckTransactionWithinDifferentDatacontext();
                CheckTimings();
                Console.WriteLine("press any key...");
                Console.ReadKey();
            }
        }

        static void CheckNestedBeginOrJoin()
        {
            const int ID = 680;

            using (IUnitOfWork unitOfWork = UnitOfWork.Begin())
            {
                unitOfWork.GetRepository<Product>().Single(x => x.ProductID == ID);

                using (IUnitOfWork unitOfWork2 = UnitOfWork.Begin())
                    unitOfWork2.GetRepository<Product>().Single(x => x.ProductID == ID);

                using (IUnitOfWork unitOfWork2 = UnitOfWork.BeginOrJoin())
                    unitOfWork2.GetRepository<Product>().Single(x => x.ProductID == ID);

                using (IUnitOfWork unitOfWork2 = UnitOfWork.BeginOrJoin())
                    unitOfWork2.GetRepository<Product>().Single(x => x.ProductID == ID);
            }
        }

        static void CheckTransactionWithinDifferentDatacontext()
        {
            const int ID = 680;

            using (IUnitOfWork unitOfWork = UnitOfWork.Begin())
            {
                IRepository<Product> productRepository = unitOfWork.GetRepository<Product>();
                Product product = productRepository.GetById(ID);
                string oldName = product.Name;
                string newName = oldName + "2";
                product.Name = newName;

                using (IUnitOfWork unitOfWork2 = UnitOfWork.Begin())
                    if (unitOfWork2.GetRepository<Product>().GetById(ID).Name != oldName)
                        throw new Exception("productname should be equal to oldName");

                using (IUnitOfWork unitOfWork3 = UnitOfWork.Begin(UnitOfWork.CurrentDataContext, TransactionScopeOption.RequiresNew))
                    unitOfWork3.Commit();

                using (IUnitOfWork unitOfWork2 = UnitOfWork.Begin())
                    if (unitOfWork2.GetRepository<Product>().GetById(ID).Name != newName)
                        throw new Exception("productname should be equal to newName");
            }
        }

        static void CheckTimings()
        {
            Console.WriteLine("\n\n");
            const int COUNT = 100;

            using (IUnitOfWork unitOfWork = UnitOfWork.Begin())
            {
                IRepository<Product> productRepository = unitOfWork.GetRepository<Product>();

                Product p = new Product { ProductNumber = "number", Name = "test" };
                ICollection<ValidationResult> validationResults = new List<ValidationResult>();
                if (!productRepository.Validate(p, validationResults))
                    foreach (ValidationResult validationResult in validationResults)
                        Console.WriteLine(validationResult.ErrorMessage);

                const int ID = 680;
                DateTime start;
                DateTime end;

                //first try, no output, just open the connection and get rid of startup deplays
                Console.WriteLine(new QueryAllProducts().Execute().Count());
                Console.WriteLine(new QueryTest().Execute().Count());
                Console.WriteLine(new QueryTest().Execute().First().ProductCategory.Name);
                Console.WriteLine(new QueryTest().Execute().First().SalesOrderDetails.Count());
                Console.WriteLine(new QueryProductById().Execute(ID).Name);
                Console.WriteLine(new QueryProductById680WithSpec().Execute().Name);
                Console.WriteLine(new QueryAllProductsInclude().Execute().First().ProductCategory.Name);

                start = DateTime.Now;
                for (int i = 0; i < COUNT; i++)
                    productRepository.Single(x => x.ProductID == ID);
                end = DateTime.Now;
                Console.WriteLine("query id      {0}".FormatWith((end - start).TotalMilliseconds));

                start = DateTime.Now;
                for (int i = 0; i < COUNT; i++)
                    productRepository.GetById(ID);
                end = DateTime.Now;
                Console.WriteLine("repo id       {0}".FormatWith((end - start).TotalMilliseconds));

                start = DateTime.Now;
                for (int i = 0; i < COUNT; i++)
                    new QueryProductById().Execute(ID);
                end = DateTime.Now;
                Console.WriteLine("IQuery id     {0}".FormatWith((end - start).TotalMilliseconds));

                start = DateTime.Now;
                for (int i = 0; i < COUNT; i++)
                    new QueryProductById680WithSpec().Execute();
                end = DateTime.Now;
                Console.WriteLine("IQuerySpec id {0} (no param, --> faster)".FormatWith((end - start).TotalMilliseconds));

                start = DateTime.Now;
                for (int i = 0; i < COUNT; i++)
                    productRepository.Where(x => x.Name.StartsWith("a")).OrderBy(x => x.Name).ToArray().OfType<Product>().Count();
                end = DateTime.Now;
                Console.WriteLine("list query       {0}".FormatWith((end - start).TotalMilliseconds));

                start = DateTime.Now;
                for (int i = 0; i < COUNT; i++)
                    new QueryAllProducts().Execute().Count();
                end = DateTime.Now;
                Console.WriteLine("list IQuery      {0}".FormatWith((end - start).TotalMilliseconds));

                start = DateTime.Now;
                for (int i = 0; i < COUNT; i++)
                    new QueryAllProductsWithSpec().Execute().Count();
                end = DateTime.Now;
                Console.WriteLine("list IQuery spec {0}".FormatWith((end - start).TotalMilliseconds));

                ProductById680Specification spec = new ProductById680Specification();
                start = DateTime.Now;
                for (int i = 0; i < COUNT; i++)
                    productRepository.GetSatisfying(spec).Count();
                end = DateTime.Now;
                Console.WriteLine("list spec        {0}".FormatWith((end - start).TotalMilliseconds));

                ProductNameContainsSpecification spec2 = new ProductNameContainsSpecification();
                start = DateTime.Now;
                for (int i = 0; i < COUNT; i++)
                    productRepository.GetSatisfying(spec.And(spec2)).Count();
                end = DateTime.Now;
                Console.WriteLine("list spec2       {0}".FormatWith((end - start).TotalMilliseconds));
            }
        }
    }
}